package com.hzw.saas.service.old.hub.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.hzw.saas.common.util.enums.ConflictModeEnum;
import com.hzw.saas.service.old.hub.constants.PathConsts;
import com.hzw.saas.service.old.hub.enums.PathType;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.file.PathUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 存储工具
 * jdk 1.7+
 *
 * @author zzl
 * @since 04/20/2021
 */
@Slf4j
public class StorageUtil {


    public static String resolvePath(String path) {
        return StrUtil.isBlank(path) ? "" : path.trim();
    }

    public static String[] resolvePath(String... paths) {
        return Arrays.stream(paths).map(s -> StrUtil.isBlank(s) ? "" : s.trim()).toArray(String[]::new);
    }

    /**
     * 拼接路径
     *
     * @param paths 待拼接的路径
     * @return 连接路径
     */
    public static String connectPath(String... paths) {
        paths = resolvePath(paths);
        return Paths.get("", paths).toString();
    }

    /**
     * @param name
     * @return
     */
    public static boolean hasForbiddenChar(String name) {
        return StrUtil.isBlank(name) || name.matches(PathConsts.WIN_FORBIDDEN_NAME_PATTERN);
    }

    public static boolean isAllDir(String... paths) {
        paths = resolvePath(paths);
        Path path = Paths.get("", paths);
        while (path != null) {
            if (PathUtil.isFile(path, false))
                return false;
            path = path.getParent();
        }
        return true;
    }

    /**
     * 检查文件是否存在
     * exists = true, 实际不存在，抛出IORuntimeException（包裹FileNotFoundException）
     * exists = false, 实际存在，抛出IORuntimeException（FileAlreadyExistsException）
     *
     * @param file   待检查文件
     * @param exists 是否存在
     * @throws IORuntimeException
     */
    public static void exists(File file, boolean exists) throws IORuntimeException {
        if (!FileUtil.exist(file) && exists) {
            log.debug("The file/folder not exists: {}, throw FileNotFoundException", file.toString());
            throw new IORuntimeException(new FileNotFoundException(file.toString()));
        }

        if (FileUtil.exist(file) && !exists) {
            log.debug("The file/folder already exists: {}, throw FileAlreadyExistsException", file.toString());
            throw new IORuntimeException(new FileAlreadyExistsException(file.toString()));
        }
    }

    public static void equals(File srcFile, File tarFile, boolean equals) {
        boolean eq = FileUtil.equals(srcFile, tarFile);
        if (equals && !eq)
            throw new IORuntimeException(String.format("Files '%s' and '%s' are equal", srcFile.toString(), tarFile.toString()));
        if (!equals && eq)
            throw new IORuntimeException(String.format("Files '%s' and '%s' are not equal", srcFile.toString(), tarFile.toString()));
    }

    public static long getFileLength(File file) {
        if (!FileUtil.exist(file))
            return -1;
        return FileUtil.size(file);
    }

    public static String getFileDigest(File file, String digestType) {
        if (!FileUtil.isFile(file))
            return "";
        digestType = digestType == null ? "MD5" : digestType.trim().toLowerCase();
        if ("SHA1".equalsIgnoreCase(digestType)) {
            return DigestUtil.sha1Hex(file);
        }
        if ("SHA256".equalsIgnoreCase(digestType))
            return DigestUtil.sha256Hex(file);
        return DigestUtil.md5Hex(file);
    }

    public static String getBytesDigest(byte[] bytes, String digestType) {
        if (bytes == null)
            return "";
        digestType = digestType == null ? "MD5" : digestType.trim().toLowerCase();
        if ("SHA1".equalsIgnoreCase(digestType)) {
            return DigestUtil.sha1Hex(bytes);
        }
        if ("SHA256".equalsIgnoreCase(digestType))
            return DigestUtil.sha256Hex(bytes);
        return DigestUtil.md5Hex(bytes);
    }

    public static String renameConflictName(File file) {
        File folder = FileUtil.getParent(file, 1);
        String prefix = FileUtil.getPrefix(file);
        String suffix = FileUtil.getSuffix(file);
        // 处理文件名(不含后缀)，去除可能存在的copy后缀
        prefix = removeCopySuffix(prefix);
        suffix = addDot2FileSuffix(suffix);
        int index = 1;
        File newFile;
        do {
            String newName = prefix + copySuffix(index) + suffix;
            newFile = new File(folder, newName);
            index++;
        } while (newFile.exists());
        return FileUtil.getName(newFile);
    }

    /**
     * @param file     待检查的重名文件
     * @param mode     冲突解决模式
     * @param pathType 类型,文件/目录
     * @param create   是否新建已重命名的文件/目录，用来占位名称空间
     * @return 原文件或冲突时重命名后的文件
     */
    public static File doConflictName(File file, ConflictModeEnum mode, PathType pathType, boolean create) {
        if (file == null)
            return null;
        if (FileUtil.exist(file)) {
            String type = pathType.name().toLowerCase();
            switch (mode) {
                case THROW_ERR:
                    log.debug("The {} with the same name exist: {}, throw FileAlreadyExistsException", type, file);
                    throw new IORuntimeException(new FileAlreadyExistsException(file.toString()));
                case RENAME_SRC:
                    String newName = StorageUtil.renameConflictName(file);
                    log.info("The {} with the same name exist in path ({}), rename: {} -> {}",
                        type, file.getParent(), file.getName(), newName);
                    file = new File(file.getParentFile(), newName);
                    break;
                case COVER_TAR:
                    if (file.isFile() && pathType == PathType.FOLDER)
                        throw new IORuntimeException(new FileAlreadyExistsException("file with the same name exist: " + file));
                    if (file.isDirectory() && pathType == PathType.FILE)
                        throw new IORuntimeException(new FileAlreadyExistsException("folder with the same name exist: " + file));
                    log.info("The {} with the same name exist({}), it will be covered by the new one", type, file);
            }
        }
        if (create) {
            switch (pathType) {
                case FILE:
                    return FileUtil.touch(file);
                case FOLDER:
                    return FileUtil.mkdir(file);
            }
        }
        return file;
    }

    /**
     * 静默删除
     *
     * @param file 待删除文件
     */
    public static void silentDelete(File file) {
        try {
            FileUtil.del(file);
        } catch (Exception ignored) {}
    }

    public static void checkTarLength(File file, Long expectedLength, boolean del) {
        if (expectedLength == null)
            return;
        long length = StorageUtil.getFileLength(file);
        if (Objects.equals(expectedLength, length)) {
            log.debug("verify file length, passed: {file: {}, length: {}}", file, length);
            return;
        }
        if (del) {
            silentDelete(file);
        }
        String errorMsg = String.format("file length mismatch: {file: %s,expected: %s, actual: %s}",
            file.getPath(), expectedLength, length);
        throw new IORuntimeException(errorMsg);
    }

    public static void checkTarDigest(File file, String expectedDigest, String digestType, boolean del) {
        if (StrUtil.isBlank(expectedDigest))
            return;
        String digest = StorageUtil.getFileDigest(file, digestType);
        if (Objects.equals(expectedDigest, digest)) {
            log.debug("verify file digest, passed: {file: {}, digest: {}}", file, digest);
            return;
        }
        if (del) {
            silentDelete(file);
        }
        String errorMsg = String.format("file digest mismatch: {file: %s,expected: %s, actual: %s}",
            file.getPath(), expectedDigest, digest);
        throw new IORuntimeException(errorMsg);
    }

    public static void checkSrcLength(File file, Long expectedLength) {
        if (expectedLength == null)
            return;
        long length = StorageUtil.getFileLength(file);
        if (Objects.equals(expectedLength, length))
            return;
        String errorMsg = String.format("source file length mismatch: {file: %s,expected: %s, actual: %s}",
            file.getPath(), expectedLength, length);
        throw new IORuntimeException(errorMsg);
    }

    public static void checkSrcLength(byte[] file, Long expectedLength) {
        if (expectedLength == null)
            return;
        long length = file.length;
        if (Objects.equals(expectedLength, length))
            return;
        String errorMsg = String.format("source bytes length mismatch: {expected: %s, actual: %s}",
            expectedLength, length);
        throw new IORuntimeException(errorMsg);
    }

    public static void checkSrcDigest(File file, String expectedDigest, String digestType) {
        if (StrUtil.isBlank(expectedDigest))
            return;
        String digest = StorageUtil.getFileDigest(file, digestType);
        if (Objects.equals(expectedDigest, digest))
            return;
        String errorMsg = String.format("source file digest mismatch: {file: %s,expected: %s, actual: %s}",
            file.getPath(), expectedDigest, digest);
        throw new IORuntimeException(errorMsg);
    }

    public static void checkSrcDigest(byte[] file, String expectedDigest, String digestType) {
        if (StrUtil.isBlank(expectedDigest))
            return;
        String digest = StorageUtil.getBytesDigest(file, digestType);
        if (Objects.equals(expectedDigest, digest))
            return;
        String errorMsg = String.format("source bytes digest mismatch: {expected: %s, actual: %s}",
            expectedDigest, digest);
        throw new IORuntimeException(errorMsg);
    }


    private static String copySuffix(int index) {
        return String.format(PathConsts.COPY_SUFFIX_FORMAT, index);
    }

    private static String removeCopySuffix(String name) {
        if (StrUtil.isBlank(name))
            return name;
        Pattern pattern = Pattern.compile(PathConsts.COPY_SUFFIX_PATTERN, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(name);
        return matcher.find() ? matcher.group(1) : name;
    }

    private static String addDot2FileSuffix(String suffix) {
        if (StrUtil.isNotBlank(suffix) && !suffix.startsWith("."))
            suffix = "." + suffix;
        return suffix;
    }
}
